/**
 * Created : Mar 30, 2012
 *
 * @author pquiring
 */

import java.io.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

import javaforce.*;
import javaforce.jbus.*;
import javaforce.linux.*;
import javaforce.utils.*;

import jffile.*;

public class Dock extends javax.swing.JWindow implements ActionListener, MouseListener, MouseMotionListener, LayoutManager, X11Listener, monitordir.Listener, FileClipboard {

  /**
   * Creates new form Dock
   */
  public Dock() {
    try {
      initComponents();
      IconCache.setPrefix("jfdesktop-");
      arrowImage = IconCache.loadIcon("jfdesktop-arrow");
      loadNetworkIcons();
      dock = this;
      x11id = Linux.x11_get_id(this);
      JFLog.log("Dock.window=0x" + Long.toString(x11id, 16));
      try {
        Linux.x11_set_dock(x11id);
      } catch (Throwable t) {
        JFLog.log(t);
      }
      addTo = buttons;
      loadConfig();
      addAppsButton();
      addArrows();
      getDimensions();
      loadButtons();
      DisplayMode screen_mode = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
      try {
        Linux.x11_set_strut(x11id, (config.autoHide ? 1 : panelHeight-borderSize), 0, 0, screen_mode.getWidth(), screen_mode.getHeight());
      } catch (Throwable t) {
        JFLog.log(t);
      }
      JF.sleep(100);
      getScreenSize();
      soundinit();
      initUpgrades();
      loadGlobalConfig();
      addClock();
      setClock();
      addTrash();
      addKeyboard();
      addQuadControls();
      addHalfControls();
      setLayout(this);
      buttons.setLayout(this);
      setSize(sx, 1);
      setLocation(0, sy-1);
      calcDockHeight();
      updateConfig();
      if (config.autoHide) showDock();
      DesktopCache.buildCache();
      setupClockTimer();
      addMouseListener(this);
      addMouseListener(TrashPopup);
      addMouseListener(PowerPopup);
      addMouseListener(ConfigPopup);
      addMouseListener(SoundPopup);
      addMouseListener(ClockPopup);
      addMouseListener(TaskbarPopup);
      //connect to JBus
      JFLog.log("jbusClient:package=org.jflinux.jfdesktop." + System.getenv("JID"));
      jbusClient = new JBusClient("org.jflinux.jfdesktop." + System.getenv("JID"), new JBusMethods());
      jbusClient.start();
      if (new File("/usr/sbin/hciconfig").exists()) {
        checkBluetooth();
      }
      if (new File("/usr/bin/jfwelcome").exists()) {
        if (config.welcome) {
          showWelcome();
        }
      }
      startTrashListener();
      mkdirs();
      keyboardWindow = new KeyboardWindow();
      Linux.x11_set_listener(this);
      new Thread() {
        public void run() {
          try {
            //monitor system tray
            Linux.x11_tray_main(x11id, sx, trayPos, buttonHeight + 4);
          } catch (Throwable t) {
            JFLog.log(t);
          }
        }
      }.start();
      new Thread() {
        public void run() {
          try {
            //monitor when top-level windows change
            Linux.x11_window_list_main();
          } catch (Throwable t) {
            JFLog.log(t);
          }
        }
      }.start();
      JF.sleep(250);  //wait for threads to start
      if (new File("/usr/bin/acpi").exists()) {
        checkBattery();
      }
      try {
        Linux.x11_set_dock(x11id);
      } catch (Throwable t) {
        JFLog.log(t);
      }
      new Thread() {
        public void run() {
          loadMappings();  //can take a while
        }
      }.start();
      initDockDND();
      getWAPList();
      JFLog.log("Dock init complete");
    } catch (Throwable t) {
      JFLog.log(t);
    }
  }

  /**
   * This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is always
   * regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    TrashPopup = new javax.swing.JPopupMenu();
    EmptyTrash = new javax.swing.JMenuItem();
    PowerPopup = new javax.swing.JPopupMenu();
    Logoff = new javax.swing.JMenuItem();
    Reboot = new javax.swing.JMenuItem();
    Shutdown = new javax.swing.JMenuItem();
    Sleep = new javax.swing.JMenuItem();
    ConfigPopup = new javax.swing.JPopupMenu();
    ControlCenter = new javax.swing.JMenuItem();
    DesktopSettings = new javax.swing.JMenuItem();
    Upgrades = new javax.swing.JMenuItem();
    Run = new javax.swing.JMenuItem();
    Help = new javax.swing.JMenuItem();
    SoundPopup = new javax.swing.JPopupMenu();
    Settings = new javax.swing.JMenuItem();
    ClockPopup = new javax.swing.JPopupMenu();
    AdjustTime = new javax.swing.JMenuItem();
    BatteryPopup = new java.awt.PopupMenu();
    BatterySettings = new java.awt.MenuItem();
    TaskbarPopup = new javax.swing.JPopupMenu();
    MinimizeAll = new javax.swing.JMenuItem();
    jSeparator1 = new javax.swing.JPopupMenu.Separator();
    TaskMgr = new javax.swing.JMenuItem();
    jSeparator2 = new javax.swing.JPopupMenu.Separator();
    DockSettings = new javax.swing.JMenuItem();
    buttons = new javax.swing.JPanel();

    EmptyTrash.setText("Empty");
    EmptyTrash.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        EmptyTrashActionPerformed(evt);
      }
    });
    TrashPopup.add(EmptyTrash);

    Logoff.setText("Logff");
    Logoff.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        LogoffActionPerformed(evt);
      }
    });
    PowerPopup.add(Logoff);

    Reboot.setText("Reboot");
    Reboot.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        RebootActionPerformed(evt);
      }
    });
    PowerPopup.add(Reboot);

    Shutdown.setText("Shutdown");
    Shutdown.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        ShutdownActionPerformed(evt);
      }
    });
    PowerPopup.add(Shutdown);

    Sleep.setText("Sleep");
    Sleep.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        SleepActionPerformed(evt);
      }
    });
    PowerPopup.add(Sleep);

    ControlCenter.setText("Control Center");
    ControlCenter.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        ControlCenterActionPerformed(evt);
      }
    });
    ConfigPopup.add(ControlCenter);

    DesktopSettings.setText("Desktop Settings");
    DesktopSettings.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        DesktopSettingsActionPerformed(evt);
      }
    });
    ConfigPopup.add(DesktopSettings);

    Upgrades.setText("Upgrades");
    Upgrades.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        UpgradesActionPerformed(evt);
      }
    });
    ConfigPopup.add(Upgrades);

    Run.setText("Run");
    Run.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        RunActionPerformed(evt);
      }
    });
    ConfigPopup.add(Run);

    Help.setText("Help");
    Help.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        HelpActionPerformed(evt);
      }
    });
    ConfigPopup.add(Help);

    Settings.setText("Settings");
    Settings.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        SettingsActionPerformed(evt);
      }
    });
    SoundPopup.add(Settings);

    AdjustTime.setText("Adjust Date & Time");
    AdjustTime.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        AdjustTimeActionPerformed(evt);
      }
    });
    ClockPopup.add(AdjustTime);

    BatteryPopup.setName("");

    BatterySettings.setLabel("Settings");
    BatterySettings.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        BatterySettingsActionPerformed(evt);
      }
    });
    BatteryPopup.add(BatterySettings);

    MinimizeAll.setText("Show the desktop");
    MinimizeAll.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        MinimizeAllActionPerformed(evt);
      }
    });
    TaskbarPopup.add(MinimizeAll);
    TaskbarPopup.add(jSeparator1);

    TaskMgr.setText("Start Task Manager");
    TaskMgr.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        TaskMgrActionPerformed(evt);
      }
    });
    TaskbarPopup.add(TaskMgr);
    TaskbarPopup.add(jSeparator2);

    DockSettings.setText("Settings");
    DockSettings.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        DockSettingsActionPerformed(evt);
      }
    });
    TaskbarPopup.add(DockSettings);

    setAlwaysOnTop(true);
    setMinimumSize(new java.awt.Dimension(1, 1));
    setName("dock"); // NOI18N

    buttons.setMinimumSize(new java.awt.Dimension(1, 1));
    buttons.setPreferredSize(new java.awt.Dimension(1, 1));

    javax.swing.GroupLayout buttonsLayout = new javax.swing.GroupLayout(buttons);
    buttons.setLayout(buttonsLayout);
    buttonsLayout.setHorizontalGroup(
      buttonsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGap(0, 545, Short.MAX_VALUE)
    );
    buttonsLayout.setVerticalGroup(
      buttonsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGap(0, 1, Short.MAX_VALUE)
    );

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(buttons, javax.swing.GroupLayout.DEFAULT_SIZE, 545, Short.MAX_VALUE)
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(buttons, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
    );

    pack();
  }// </editor-fold>//GEN-END:initComponents

  private void EmptyTrashActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_EmptyTrashActionPerformed
    emptyTrash();
  }//GEN-LAST:event_EmptyTrashActionPerformed

  private void LogoffActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_LogoffActionPerformed
    if (!JFAWT.showConfirm("Confirm", "Are you sure you want to logoff?")) return;
    closeAllApps();
    System.exit(0);
  }//GEN-LAST:event_LogoffActionPerformed

  private void RebootActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_RebootActionPerformed
    if (!JFAWT.showConfirm("Confirm", "Are you sure you want to reboot?")) return;
    jbusClient.call("org.jflinux.jfsystemmgr", "reboot", "");
    closeAllApps();
    System.exit(0);
  }//GEN-LAST:event_RebootActionPerformed

  private void ShutdownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ShutdownActionPerformed
    if (!JFAWT.showConfirm("Confirm", "Are you sure you want to shutdown?")) return;
    jbusClient.call("org.jflinux.jfsystemmgr", "shutdown", "");
    closeAllApps();
    System.exit(0);
  }//GEN-LAST:event_ShutdownActionPerformed

  private void ControlCenterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ControlCenterActionPerformed
    try {
      Runtime.getRuntime().exec("jconfig");
    } catch (Exception e) {
      JFLog.log(e);
    }
  }//GEN-LAST:event_ControlCenterActionPerformed

  private void DesktopSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_DesktopSettingsActionPerformed
    showConfig();
  }//GEN-LAST:event_DesktopSettingsActionPerformed

  private void SleepActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_SleepActionPerformed
    //disconnect all VPN
    jbusClient.call("org.jflinux", "closeAllVPN", "");
    JF.sleep(500);
    jbusClient.call("org.jflinux.jfsystemmgr", "sleep" , "");
    //reset clock after 10 seconds (hopefully after we resume from sleep)
    new java.util.Timer().schedule(new TimerTask() {
      public void run() {
        setClock();
      }
    }, 10 * 1000);
  }//GEN-LAST:event_SleepActionPerformed

  private void SettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_SettingsActionPerformed
    try {
      Runtime.getRuntime().exec(new String[] {"jconfig", "sound"});
    } catch (Exception e) {
      JFLog.log(e);
    }
  }//GEN-LAST:event_SettingsActionPerformed

  private void AdjustTimeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_AdjustTimeActionPerformed
    try {
      Runtime.getRuntime().exec(new String[] {"jconfig", "datetime"});
    } catch (Exception e) {
      JFLog.log(e);
    }
  }//GEN-LAST:event_AdjustTimeActionPerformed

  private void HelpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_HelpActionPerformed
    try {
      Runtime.getRuntime().exec(new String[] {"jhelp", "jfdesktop"});
    } catch (Exception e) {
      JFLog.log(e);
    }
  }//GEN-LAST:event_HelpActionPerformed

  private void RunActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_RunActionPerformed
    doRun();
  }//GEN-LAST:event_RunActionPerformed

  private void UpgradesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_UpgradesActionPerformed
    try { Runtime.getRuntime().exec("jupgrade"); } catch (Exception e) {JFLog.log(e);}
  }//GEN-LAST:event_UpgradesActionPerformed

  private void BatterySettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_BatterySettingsActionPerformed
    new BatteryDialog(null, true).setVisible(true);
  }//GEN-LAST:event_BatterySettingsActionPerformed

  private void DockSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_DockSettingsActionPerformed
    showConfig();
  }//GEN-LAST:event_DockSettingsActionPerformed

  private void MinimizeAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MinimizeAllActionPerformed
    Linux.x11_minimize_all();
  }//GEN-LAST:event_MinimizeAllActionPerformed

  private void TaskMgrActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_TaskMgrActionPerformed
    try {
      Runtime.getRuntime().exec("jtaskmgr");
    } catch (Exception e) {
      JFLog.log(e);
    }
  }//GEN-LAST:event_TaskMgrActionPerformed

  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JMenuItem AdjustTime;
  private java.awt.PopupMenu BatteryPopup;
  private java.awt.MenuItem BatterySettings;
  private javax.swing.JPopupMenu ClockPopup;
  private javax.swing.JPopupMenu ConfigPopup;
  private javax.swing.JMenuItem ControlCenter;
  private javax.swing.JMenuItem DesktopSettings;
  private javax.swing.JMenuItem DockSettings;
  private javax.swing.JMenuItem EmptyTrash;
  private javax.swing.JMenuItem Help;
  private javax.swing.JMenuItem Logoff;
  private javax.swing.JMenuItem MinimizeAll;
  private javax.swing.JPopupMenu PowerPopup;
  private javax.swing.JMenuItem Reboot;
  private javax.swing.JMenuItem Run;
  private javax.swing.JMenuItem Settings;
  private javax.swing.JMenuItem Shutdown;
  private javax.swing.JMenuItem Sleep;
  private javax.swing.JPopupMenu SoundPopup;
  private javax.swing.JMenuItem TaskMgr;
  private javax.swing.JPopupMenu TaskbarPopup;
  private javax.swing.JPopupMenu TrashPopup;
  private javax.swing.JMenuItem Upgrades;
  private javax.swing.JPanel buttons;
  private javax.swing.JPopupMenu.Separator jSeparator1;
  private javax.swing.JPopupMenu.Separator jSeparator2;
  // End of variables declaration//GEN-END:variables

  public static class App {
    public String file;
  }

  public static class Card {
    public int idx;
    public String activeProfile;
  }

  public static class Sink {
    public int idx;
    public String activePort;
    public int volume;  //%
  }

  public static class Source {
    public int idx;
    public String activePort;
    public int volume;  //%
  }

  public static class Config {
    public Config() {
      dock = new App[0];
    }
    public boolean showClock;
    public boolean showKeyboard;
    public boolean autoHide;
    public boolean welcome;
    public boolean compact;
    public boolean showIcons;  //on Desktop
    public boolean arrangeIconsAuto;  //on Desktop
    public boolean mountAudio;
    public int desktopMode;
    public String desktopFile;
    public App dock[];  //for Dock
    public Color bc, fc;  //back fore color (desktop)
    //sound config
    public Card card[];
    public Sink sink[];
    public Source source[];
    public int dockSize;  //48, 32, 24
    //video config (TODO) - for now in .xrandr.xml
  }

  public static class GlobalConfig {
    public boolean disableSleep;
  }

  public static class BlueToothDevice {
    String desc, mac;
  }

  public Config config = new Config();
  private String configFile = ".jfdesktop.xml";
  private GlobalConfig globalConfig = new GlobalConfig();
  private String globalConfigFolder = "/etc/jconfig.d/";
  private String globalConfigFile = "global.xml";
  private int appIdx = 0;  //where to insert next button
  private int viewIdx = 0;  //first app to show if not all fit on dock
  private int overflowCnt = 0;
  private boolean needArrows = false;
  public static int sx, sy;  //screen size
  private JFImage clockImage;
  private Font clockFont;
  private String clockLine1 = "MONTH DAY", clockLine2 = "HH:MM";
  private JButton left, right, apps, trash, keyboard;
  private JButton soundQuad, networkQuad, powerQuad, settingsQuad;
  private JButton soundHalf, networkHalf, powerHalf, settingsHalf;
  private JFImage batteryImage, trashFullImage, trashEmptyImage;
  private JPanel quad;
  private SystemTray tray;
  private TrayIcon trayIcon;
  private boolean haveMouse = true;
  private boolean isDockHidden = true;
  private int panelHeight, buttonWidth, buttonHeight, halfWidth;
  private int panelHiddenHeight = 1;
  private CalendarWindow calWindow;
  private SoundWindow soundWindow;
  public KeyboardWindow keyboardWindow;
  private boolean keyboardShown = false;
  public static JBusClient jbusClient;
  private boolean isTrashFull;
  public static BlueToothDialog btDialog;
  private java.util.Timer clockTimer;
  private java.util.Timer flashTimer;
  private java.util.Timer autoHideTimer;
  private JPopupMenu NetworkPopup = new JPopupMenu();
  public static Dock dock;
  private long x11id;
  public static ArrayList<BlueToothDevice> btDevices = new ArrayList<BlueToothDevice>();
  private int buttonsCount = 0;  //# of app buttons shown in dock (pinned & !pinned)
  private String fileSelection = "";
  private static final int borderSize = 4;
  private int trayPos = 0;
  private final int trayPad = 2;
  private JButton newButton;
  private int newButtonIdx;

  public void trayIconAdded(int count) {
    JFLog.log("Dock:tray icon added");
  }

  public void trayIconRemoved(int count) {
    JFLog.log("Dock:tray icon removed");
  }

  private boolean updatePending = false;

  public void windowsChanged() {
    if (updatePending) return;
    updatePending = true;
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        updateWindowList();
      }
    });
  }

  private static class Window {
    public long xid;
    public String title;
    public Window(long xid, String title) {
      this.xid = xid;
      this.title = title;
    }
    public void show() {
      Linux.x11_map_window(xid);
      Linux.x11_raise_window(xid);
    }
  }

  private static class Group {
    public ArrayList<Window> windows = new ArrayList<Window>();
    public void updateTitle(long xid, String title) {
      for(int a=0;a<windows.size();a++) {
        Window window = windows.get(a);
        if (window.xid == xid) {
          window.title = title;
          return;
        }
      }
      addWindow(xid, title);
    }
    public void addWindow(long xid, String title) {
      windows.add(new Window(xid, title));
    }
    public void clearWindows() {
      windows.clear();
    }
    public void removeWindow(int idx) {
      windows.remove(idx);
    }
    public void removeWindow(long xid) {
      for(int a=0;a<windows.size();a++) {
        Window window = windows.get(a);
        if (window.xid == xid) {
          windows.remove(a);
          return;
        }
      }
    }
    public boolean isEmpty() {
      return windows.isEmpty();
    }
    public Window getWindow(int idx) {
      return windows.get(idx);
    }
    public int size() {
      return windows.size();
    }
  }

  //updates the active windows list
  private synchronized void updateWindowList() {
    //add buttons
    updatePending = false;
    try {
      Linux.Window winList[] = Linux.x11_get_window_list();
      Component buts[] = buttons.getComponents();
      for(int w=0;w<winList.length;w++) {
        Linux.Window x11window = winList[w];
//        JFLog.log("window:xid=" + Integer.toString(JF.atoi(x11window.xid.toString()), 16) + ",pid=" + x11window.pid + ",title=" + x11window.title + ",res_class=" + x11window.res_class + ",res_name=" + x11window.res_name);
        x11window.file = null;
        if (x11window.pid != -1) x11window.file = DesktopCache.getDesktopFromPID(x11window.pid);
        if (x11window.file == null && x11window.title.length() > 0) x11window.file = DesktopCache.getDesktopFromText(x11window.title);
        if (x11window.file == null && x11window.res_class.length() > 0) x11window.file = DesktopCache.getDesktopFromText(x11window.res_class);
        if (x11window.file == null) {
          //unable to match window (TODO : create a generic button? : would need to copy icon)
          JFLog.log("Dock:Unable to match window to application:" + x11window.title);
          continue;
        }
        boolean ok = false;
        for(int b=0;b<buts.length;b++) {
          if (!(buts[b] instanceof JButton)) continue;
          JButton button = (JButton)buts[b];
          String file = (String)button.getClientProperty("file");
          if (file.startsWith("#")) continue;
          Group group = (Group)button.getClientProperty("group");
          if (file.equals(x11window.file)) {
            group.updateTitle(x11window.xid, x11window.title);
            ok = true;
            break;
          }
        }
        if (ok) continue;
        //add new button
        JButton button = addButton(x11window.file, false, -1);
        Group group = (Group)button.getClientProperty("group");
        group.addWindow(x11window.xid, x11window.title);
        buts = buttons.getComponents();
      }
      //remove buttons
      for(int b=0;b<buts.length;b++) {
        if (!(buts[b] instanceof JButton)) continue;
        JButton button = (JButton)buts[b];
        String file = (String)button.getClientProperty("file");
        if (file.startsWith("#")) continue;
        Group group = (Group)button.getClientProperty("group");
        boolean group_ok = false;
        for(int g=0;g<group.size();) {
          Window window = group.getWindow(g);
          boolean window_ok = false;
          for(int w=0;w<winList.length;w++) {
            Linux.Window x11window = winList[w];
            if (x11window.xid == window.xid) {group_ok = true; window_ok = true; break;}
          }
          if (!window_ok) {
            group.removeWindow(g);
          } else {
            g++;
          }
        }
        if (group_ok) {
          createAppPopupMenu(button);
          continue;
        }
        //remove button (if not pinned)
        Boolean pinned = (Boolean)button.getClientProperty("pinned");
        if (!pinned) {
          removeButton(button);
          //NOTE:do NOT update buts list
        } else {
          createAppPopupMenu(button);  //remove any windows
        }
      }
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private void togglePinned(JButton button) {
    String file = (String)button.getClientProperty("file");
    Boolean pinned = (Boolean)button.getClientProperty("pinned");
    Group group = (Group)button.getClientProperty("group");
    if (pinned) {
      if (group.isEmpty()) {
        removeButton(button);
      } else {
        button.putClientProperty("pinned", new Boolean(false));
      }
    } else {
      button.putClientProperty("pinned", new Boolean(true));
      addApp(file);
      saveConfig();
    }
    createAppPopupMenu(button);
  }

  public void loadConfig() {
    config.card = new Card[0];
    config.sink = new Sink[0];
    config.source = new Source[0];
    config.dock = new App[0];
    config.showClock = true;
    config.showKeyboard = true;
    config.dockSize = 48;
    try {
      XML xml = new XML();
      FileInputStream fis = new FileInputStream(JF.getUserPath() + "/" + configFile);
      byte data[] = JF.readAll(fis);
      String str = new String(data);
      str = str.replaceAll("app>", "dock>");  //convert pre 6.5 config
      xml.read(new ByteArrayInputStream(str.getBytes()));
      //remove old "desktop" config
      for(int a=0;a<xml.root.getChildCount();) {
        if (xml.root.getChildAt(a).getName().equals("desktop")) {
          xml.root.remove(a);
        } else {
          a++;
        }
      }
      xml.writeClass(config);
    } catch (FileNotFoundException e1) {
      defaultConfig();
    } catch (Exception e2) {
      JFLog.log(e2);
      defaultConfig();
    }
    validateConfig();
    bx = config.dockSize;
    by = config.dockSize;
  }

  public void validateConfig() {
    if (config.dockSize != 48 && config.dockSize != 32 && config.dockSize != 24) {
      config.dockSize = 48;
    }
  }

  public void getDimensions() {
    Dimension d = apps.getPreferredSize();
    buttonWidth = d.width;
    buttonHeight = d.height;
    panelHeight = d.height + borderSize * 2;
    d = left.getPreferredSize();
    halfWidth = d.width;
  }

  public void loadGlobalConfig() {
    try {
      XML xml = new XML();
      FileInputStream fis = new FileInputStream(globalConfigFolder + "/" + globalConfigFile);
      xml.read(fis);
      xml.writeClass(globalConfig);
    } catch (FileNotFoundException fnfe) {
      defaultGlobalConfig();
      return;
    } catch (Exception e) {
      defaultGlobalConfig();
      JFLog.log(e);
    }
    if (globalConfig.disableSleep) {
      PowerPopup.remove(Sleep);
      PowerPopup.remove(Reboot);
      PowerPopup.remove(Shutdown);
    }
  }

  private void defaultGlobalConfig() {
    globalConfig.disableSleep = false;
  }

  public synchronized void saveConfig() {
    try {
      XML xml = new XML();
      FileOutputStream fos = new FileOutputStream(JF.getUserPath() + "/" + configFile);
      xml.readClass("jfdesktop", config);
      xml.write(fos);
      fos.close();
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  public void defaultConfig() {
    config = new Config();
    config.showClock = true;
    config.showKeyboard = true;
    config.autoHide = true;
    config.welcome = true;
    config.showIcons = true;
    config.mountAudio = true;
    config.dock = new App[0];
    config.desktopFile = "/usr/share/icons/hicolor/48x48/apps/jfdesktop-wallpaper.png";
    config.desktopMode = 3;
    config.bc = Color.BLUE;
    config.fc = Color.WHITE;
    config.dockSize = 48;
    //add jfile
    addAppIfExists("jfile");
    //add jinstall
    if (new File("/etc/.live").exists()) {
      addAppIfExists("jinstall");
    }
    //add Chrome (if installed)
    addAppIfExists("chromium-browser");
    addAppIfExists("chrome");
    addAppIfExists("google-chrome");
    //add Firefox (if installed)
    addAppIfExists("firefox");
    //add libre Office
    addAppIfExists("libreoffice-startcenter");
    addAppIfExists("libreoffice-writer");
    addAppIfExists("libreoffice-calc");
    //add japps
    addAppIfExists("japps");
    saveConfig();
  }

  private void getScreenSize() {
    DisplayMode mode = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
    sx = mode.getWidth();
    sy = mode.getHeight();
    JFLog.log("ScreenSize:" + sx + "," + sy);
  }

  private void _setPosition() {
    if (config.autoHide) {
      setSize(sx, dockHeight);
      setLocation(0, sy - dockHeight);
    } else {
      setSize(sx, panelHeight - borderSize);
      setLocation(0, sy - (panelHeight - borderSize));
    }
  }

  private void setPosition() {
    if (java.awt.EventQueue.isDispatchThread()) {
      _setPosition();
    } else {
      try {
        java.awt.EventQueue.invokeLater(new Runnable() {
          public void run() {
            _setPosition();
          }
        });
      } catch (Exception e) {
        JFLog.log(e);
      }
    }
  }

  private void removeButton(JButton b) {
    try {
      String file = (String)b.getClientProperty("file");
      buttons.remove(b);
      appIdx--;
      if (!file.startsWith("#")) {
        buttonsCount--;
      }
      updateButtons();
      for(int a=0;a<config.dock.length;a++) {
        if (config.dock[a].file.equals(file)) {
          removeApp(a);
          break;
        }
      }
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private void copyButtonToDesktop(JButton b) {
    String src = (String)b.getClientProperty("file");
    int idx = src.lastIndexOf("/");
    String dest = JF.getUserPath() + "/Desktop" + src.substring(idx);
    boolean exists;
    int i = 1;
    do {
      exists = new File(dest).exists();
      if (exists) {
        dest = dest.substring(0, dest.length() -8) + "(" + i++ + ").desktop";
      }
    } while (exists);
    JF.copyAll(src, dest);
  }

  private void removeApp(int idx) {
    App newList[] = new App[config.dock.length - 1];
    System.arraycopy(config.dock, 0, newList, 0, idx);
    System.arraycopy(config.dock, idx+1, newList, idx, config.dock.length - idx - 1);
    config.dock = newList;
    saveConfig();
  }

  private int bx = 48, by = 48;  //button size
  private JComponent addTo;
  private JFImage buttonImage, arrowImage;
  private boolean addArrow = false;
  private boolean halfSized = false;

  public JButton addButton(String icon, String name, String file, int idx, boolean pinned) {
    buttonImage = IconCache.loadIcon(icon);
    int _bx = bx;
    int _by = by;
    if (halfSized) {
      _bx /= 2;
    }
    if (addArrow) {
      buttonImage = IconCache.scaleIcon(buttonImage, _bx, _bx);
      JFImage tmpImage = new JFImage(_bx, _by);
      tmpImage.fill(0, 0, _bx, _by, 0, true);
      tmpImage.getGraphics().drawImage(buttonImage.getImage(), 0,(_by-_bx)/2 + 4, null);
      tmpImage.getGraphics().drawImage(arrowImage.getImage(),
        0,0, _bx,_by/6,  //dest
        0,0, 24,8,  //src
        null);
      buttonImage = tmpImage;
    } else {
      buttonImage = IconCache.scaleIcon(buttonImage, _bx, _by);
    }
    JButton button = new JButton(buttonImage);
    button.setMargin(new Insets(0,0,0,0));
    button.setActionCommand(file);
    button.setContentAreaFilled(false);
    button.addActionListener(this);
//    button.setToolTipText(name);  //mouse over the tooltip causes dock to hide
    button.addMouseListener(this);
    button.putClientProperty("file", file);
    button.putClientProperty("icon", icon);
    button.putClientProperty("arrow", new Boolean(addArrow));
    button.putClientProperty("halfSized", new Boolean(halfSized));
    button.putClientProperty("pinned", new Boolean(pinned));
    button.putClientProperty("group", new Group());
    if (!file.startsWith("#")) {
      createAppPopupMenu(button);
      button.addMouseMotionListener(this);
      buttonsCount++;
    }
    addTo.add(button, idx);
    updateButtons();
    return button;
  }

  public JButton addButton(String file, boolean pinned, int idx) {
    String name = null, icon = null;
    try {
      FileInputStream fis = new FileInputStream(file);
      byte data[] = JF.readAll(fis);
      fis.close();
      String str = new String(data);
      String lns[] = str.split("\n");
      boolean desktopEntry = false;
      for(int a=0;a<lns.length;a++) {
        if (lns[a].startsWith("[Desktop Entry]")) {
          desktopEntry = true;
          continue;
        }
        if (lns[a].startsWith("[")) desktopEntry = false;
        if (!desktopEntry) continue;
        if (lns[a].startsWith("Name=")) {
          name = lns[a].substring(5);
        }
        if (lns[a].startsWith("Icon=")) {
          icon = lns[a].substring(5);
        }
      }
      if ((name == null) || (icon == null)) {
        JFAWT.showError("Error", "Unable to add app:" + file);
        return null;
      }
      if (idx == -1) idx = appIdx++; else appIdx++;
      return addButton(icon, name, file, idx, pinned);
    } catch (Exception e) {
      JFLog.log(e);
      return null;
    }
  }

  public void updateButtonIcon(JButton b) {
    String icon = (String)b.getClientProperty("icon");
    if (icon == null) return;
    Boolean arrow = (Boolean)b.getClientProperty("arrow");
    Boolean half = (Boolean)b.getClientProperty("halfSized");
    int _bx = bx;
    int _by = by;
    if (half) {
      _bx /= 2;
    }
    buttonImage = IconCache.loadIcon(icon);
    if (arrow) {
      buttonImage = IconCache.scaleIcon(buttonImage, _bx, _bx);
      JFImage tmpImage = new JFImage(_bx, _by);
      tmpImage.fill(0, 0, _bx, _by, 0, true);
      tmpImage.getGraphics().drawImage(buttonImage.getImage(), 0,(_by-_bx)/2 + 4, null);
      tmpImage.getGraphics().drawImage(arrowImage.getImage(),
        0,0, _bx,_by/6,  //dest
        0,0, 24,8,  //src
        null);
      buttonImage = tmpImage;
    } else {
      buttonImage = IconCache.scaleIcon(buttonImage, _bx, _by);
    }
    b.setIcon(buttonImage);
  }

  public void updateButtonsIcons() {
    //resize all button icons
    Component c[] = (Component[])buttons.getComponents();
    for(int a=0;a<c.length;a++) {
      if (!(c[a] instanceof JButton)) continue;
      JButton b = (JButton)c[a];
      updateButtonIcon(b);
    }
  }

  public void loadButtons() {
    //load buttons for config.app[]
    for(int a=0;a<config.dock.length;) {
      if (addButton(config.dock[a].file, true, -1) == null) {
        removeApp(a);
      } else {
        a++;
      }
    }
  }

  public boolean haveApp(String file) {
    Component buts[] = buttons.getComponents();
    for(int a=0;a<buts.length;a++) {
      if (!(buts[a] instanceof JButton)) continue;
      JButton b = (JButton)buts[a];
      String bfile = (String)b.getClientProperty("file");
      if (bfile.equals(file)) return true;
    }
    return false;
  }

  public void updateConfig() {
    validateConfig();
    bx = config.dockSize;
    by = config.dockSize;
    updateButtonIcon(apps);
    updateButtonIcon(left);
    updateButtonIcon(right);
    getDimensions();
    DisplayMode screen_mode = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
    JFLog.log("updateConfig:screen=" + screen_mode.getWidth() + "x" + screen_mode.getHeight());
    try {
      Linux.x11_set_strut(x11id, (config.autoHide ? 1 : panelHeight-borderSize), 0, 0, screen_mode.getWidth(), screen_mode.getHeight());
    } catch (Throwable t) {
      JFLog.log(t);
    }
    getScreenSize();
    if (Desktop.desktop != null) {
      Desktop.desktop.browser.setWallPaper(config.desktopFile, config.desktopMode);
      Desktop.desktop.setPosition();
    }
    if (!config.autoHide) {
      if (isDockHidden) {
        showDock();
      } else {
        setPosition();
      }
    } else {
      if (isDockHidden) {
        setPosition();
      } else {
        hideDock();
      }
    }
    if (config.showClock) {
      buttons.add(clockImage);
    } else {
      buttons.remove(clockImage);
    }
    if (config.compact) {
      buttons.add(quad);
      buttons.remove(powerHalf);
      buttons.remove(settingsHalf);
      buttons.remove(networkHalf);
      buttons.remove(soundHalf);
    } else {
      buttons.remove(quad);
      buttons.add(powerHalf);
      buttons.add(settingsHalf);
      buttons.add(networkHalf);
      buttons.add(soundHalf);
    }
    if (config.showKeyboard) {
      buttons.add(keyboard);
    } else {
      buttons.remove(keyboard);
    }
    bx = config.dockSize;
    by = config.dockSize;
    updateButtonsIcons();
    updateButtons();
  }

  public void addAppsButton() {
    apps = addButton("jfdesktop-apps", "Add", "#apps", appIdx++, true);
  }

  public void addClock() {
    clockImage = new JFImage() {
      public void paint(Graphics g) {
        FontMetrics fm = clockImage.getFontMetrics(clockImage.getFont());
        Dimension size = clockImage.getPreferredSize();
        int height = fm.getHeight();
        g.setColor(Color.white);
        g.drawRect(0,0, size.width,size.height);
        g.setColor(Color.black);
        g.setFont(clockFont);
        if (config.dockSize >= 32) {
          int width = fm.stringWidth(clockLine1);
          g.drawBytes(clockLine1.getBytes(), 0, clockLine1.length(),
            (size.width - width) / 2, ((size.height/2 - height) / 2) + height);
          width = fm.stringWidth(clockLine2);
          g.drawBytes(clockLine2.getBytes(), 0, clockLine2.length(),
            (size.width - width) / 2, (size.height/2) + ((size.height/2 - height) / 2) + (height/2));  //BUG : last /2 shouldn't be needed but is
        } else {
          //can only do one line
          int width = fm.stringWidth(clockLine2);
          g.drawBytes(clockLine2.getBytes(), 0, clockLine2.length(),
            (size.width - width) / 2, ((size.height - height)/2) + height);
        }
      }
      public Dimension getPreferredSize() {
        Dimension size = new Dimension();
        FontMetrics fm = clockImage.getFontMetrics(clockImage.getFont());
        size.width = fm.stringWidth("88:88:88 AM");
        size.height = buttonHeight;
        return size;
      }
    };
    clockFont = new Font(null, Font.BOLD, 12);
    clockImage.setComponentPopupMenu(ClockPopup);
    clockImage.addMouseListener(this);
    clockImage.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent me) {
        try {
          if (calWindow == null) showCalendar(); else hideCalendar();
        } catch (Exception e) {JFLog.log(e);}
      }
    });
    if (config.showClock) buttons.add(clockImage);
  }

  public int getLastIdx() {
    return buttons.getComponentCount()-1;
  }

  public void folderChangeEvent(String event, String file) {
    if (event.equals("CREATED")) {
      trashFull();
    } else if (event.equals("DELETED")) {
      if (!isTrashFull()) {
        trashEmpty();
      }
    }
  }

  private int monitor;

  private void startTrashListener() {
    File file = new File(System.getenv("HOME") + "/.local/share/Trash");
    file.mkdirs();
    JFLog.log("Starting Trash monitor");
    monitor = monitordir.add(file.getAbsolutePath());
    monitordir.setListener(monitor, this);
  }

  private boolean isTrashFull() {
    File file = new File(JF.getUserPath() + "/.local/share/Trash");
    if (!file.exists()) return false;
    File files[] = file.listFiles();
    if (files == null) return false;
    return files.length > 0;
  }

  public void trashFull() {
    if (isTrashFull) return;
    isTrashFull = true;
    trash.setIcon(trashFullImage);
    validate();
    repaint();
  }

  public void trashEmpty() {
    isTrashFull = false;
    trash.setIcon(trashEmptyImage);
    validate();
    repaint();
  }

  private void addTrash() {
    trashEmptyImage = IconCache.scaleIcon(IconCache.loadIcon("jfdesktop-trash-empty"), bx, by);
    trashFullImage = IconCache.scaleIcon(IconCache.loadIcon("jfdesktop-trash-full"), bx, by);
    trash = addButton((isTrashFull() ? "jfdesktop-trash-full" : "jfdesktop-trash-empty"), "Trash", "#trash", getLastIdx(), true);
    trash.setComponentPopupMenu(TrashPopup);
    initTrashDND();
  }

  private void addKeyboard() {
    addArrow = true;
    halfSized = true;
    keyboard = addButton("jfdesktop-keyboard", "Keyboard", "#keyboard", getLastIdx(), true);
    bx = config.dockSize;
    addArrow = false;
    halfSized = false;
  }

  private void addQuadControls() {
    quad = new JPanel();
    quad.setLayout(new GridLayout(2,2));
    addTo = quad;
    halfSized = true;
    powerQuad = addButton("jfdesktop-power", "Power", "#power", 0, true);
    powerQuad.setComponentPopupMenu(PowerPopup);
    settingsQuad = addButton("jfdesktop-config", "Settings", "#config", 0, true);
    settingsQuad.setComponentPopupMenu(ConfigPopup);
    networkQuad = addButton("jfdesktop-network", "Network", "#network", 0, true);
    soundQuad = addButton("jfdesktop-sound", "Sound", "#sound", 0, true);
    soundQuad.setComponentPopupMenu(SoundPopup);
    addTo = buttons;
    halfSized = false;
    if (config.compact) buttons.add(quad, getLastIdx());
  }

  private void addHalfControls() {
    if (config.compact) addTo = new JPanel();
    addArrow = true;
    halfSized = true;
    powerHalf = addButton("jfdesktop-power", "Power", "#power", 0, true);
    powerHalf.setComponentPopupMenu(PowerPopup);
    settingsHalf = addButton("jfdesktop-config", "Settings", "#config", 0, true);
    settingsHalf.setComponentPopupMenu(ConfigPopup);
    networkHalf = addButton("jfdesktop-network", "Network", "#network", 0, true);
    soundHalf = addButton("jfdesktop-sound", "Sound", "#sound", 0, true);
    soundHalf.setComponentPopupMenu(SoundPopup);
    addTo = buttons;
    addArrow = false;
    halfSized = false;
  }

  public void addArrows() {
    halfSized = true;
    left = addButton("jfdesktop-left", "Left", "#left", getLastIdx(), true);
    buttons.remove(left);
    right = addButton("jfdesktop-right", "Right", "#right", getLastIdx(), true);
    buttons.remove(right);
    halfSized = false;
  }

  private void addApp(String desktop) {
    App app = new App();
    app.file = desktop;
    App newList[] = new App[config.dock.length + 1];
    System.arraycopy(config.dock, 0, newList, 0, config.dock.length);
    newList[config.dock.length] = app;
    config.dock = newList;
  }

  private void addAppIfExists(String filename) {
    File fullPath = new File("/usr/share/applications/" + filename + ".desktop");
    if (fullPath.exists()) addApp(fullPath.getAbsolutePath());
  }

  private int calcMenuHeight(JPopupMenu menu) {
    return menu.getComponentCount() * 18 + 9;
  }

  private AddAppWindow appsDialog;

  public void addAppDialogClosed() {
    appsDialog = null;
  }

  public void actionPerformed(ActionEvent ae) {
    try {
      if (dragged) {
        dragged = false;
        return;
      }
      String action = ae.getActionCommand();
      if (calWindow != null) {
        hideCalendar();
      }
      if ((soundWindow != null) && (!action.equals("#sound"))) {
        hideSound();
      }
      if (action.equals("#battery")) {
        updateBattery();
        return;
      }
      if (action.equals("#new")) {
        //BUG : user should never be able to click this
        removeNewButton();
        updateButtons();
        return;
      }
      if (action.equals("#apps")) {
        if (appsDialog != null) {
          appsDialog.toFront();
        } else {
          appsDialog = new AddAppWindow();
          appsDialog.setVisible(true);
        }
        return;
      }
      if (action.equals("#bt-connect")) {
        btDialog = new BlueToothDialog();
        btDialog.setVisible(true);
        return;
      }
      if (action.equals("#vpn-configure")) {
        try {
          Runtime.getRuntime().exec(new String[] {"jconfig", "vpn"});
        } catch (Exception e) {
          JFLog.log(e);
        }
        return;
      }
      if (action.startsWith("#bt#")) {
        blueToothDisconnect(action.substring(4));
        return;
      }
      if (action.equals("#sound")) {
        try {
          if (soundWindow == null) showSound(); else hideSound();
        } catch (Exception e) {JFLog.log(e);}
        return;
      }
      if (action.equals("#network")) {
        if (networkTimer != null) {
          cancelNetworkConnection();
        } else {
          getVPNList();  //triggers showNetworkPopup()
        }
        return;
      }
      if (action.equals("#config")) {
        showSettingsPopup();
        return;
      }
      if (action.equals("#left")) {
        if (viewIdx > 0) {
          viewIdx--;
          overflowCnt++;
          updateButtons();
        }
        return;
      }
      if (action.equals("#right")) {
        if (overflowCnt > 0) {
          viewIdx++;
          overflowCnt--;
          updateButtons();
        }
        return;
      }
      if (action.equals("#trash")) {
        try {
          Runtime.getRuntime().exec(new String[] {"jfile", JF.getUserPath() + "/.local/share/Trash"});
        } catch (Exception e) {}
        return;
      }
      if (action.equals("#keyboard")) {
        if (!keyboardShown)
          showKeyboard();
        else
          hideKeyboard();
        return;
      }
      if (action.equals("#power")) {
        PowerPopup.show(config.compact ? powerQuad : powerHalf, 0, -calcMenuHeight(PowerPopup));
        return;
      }
      if (action.startsWith("#vpn#")) {
        connectVPN(action.substring(5));
        return;
      }
      if (action.startsWith("#wap#")) {
        int idx = Integer.valueOf(action.substring(5));
        WAP wap = wapItems.get(idx);
        connectWAP(wap.dev, wap.ssid, wap.encType);
        return;
      }
      if (action.equals("#wap-disconnect")) {
        jbusClient.call("org.jflinux.jnetworkmgr", "disconnectWAP" ,"");
        return;
      }
      Object obj = ae.getSource();
      if (obj instanceof JButton) {
        //check if window is already active for this app and show it
        JButton button = (JButton)obj;
        Group group = (Group)button.getClientProperty("group");
        String open = (String)button.getClientProperty("open");  //open'ing a new window?
        if (!group.isEmpty() && open == null) {
          group.getWindow(0).show();
          return;
        } else {
          flashButton((JButton)obj);
        }
      }
      File file = new File(action);
      if (file.isDirectory()) {
        //open with jfile
        Runtime.getRuntime().exec(new String[] {"jfile", action});
        return;
      }
      if (action.endsWith(".lnk")) {
        //open with Wine (if installed)
        Runtime.getRuntime().exec(new String[] {"wine", action});
        return;
      }
      if (!action.endsWith(".desktop")) {
        //regular file, open with jfopen
        Runtime.getRuntime().exec(new String[] {"jfopen", action});
        return;
      }
      //.desktop file, get Exec= line and execute it
      Linux.executeDesktop(action, null);
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  public void flashButton(JButton button) {
    if (flashTimer != null) return;
    flashTimer = new java.util.Timer();
    flashTimer.schedule(new TimerTask() {
      private JButton button;
      private int cnt = 7, fullcnt = 6;
      private int clr;
      private int org = 0;
      private boolean brighter = false;
      public void run() {
        if (org == 0) {
          org = getBackground().getRGB();
          clr = org;
          button.setContentAreaFilled(true);
        }
        if (brighter) clr += 0x110011; else clr -= 0x110011;
        cnt--;
        if (cnt == 0) {
          cnt = 7;
          brighter = !brighter;
          fullcnt--;
          if (fullcnt == 0) {
            flashTimer.cancel();
            flashTimer = null;
            clr = org;
            button.setContentAreaFilled(false);
          }
        }
        java.awt.EventQueue.invokeLater(new Runnable() {
          public void run() {
            button.setBackground(new Color(clr));
            button.repaint();
          }
        });
      }
      public TimerTask init(JButton button) {
        this.button = button;
        return this;
      }
    }.init(button), 100, 100);
  }

  private String vpnList = "";
  private String wapList = "";

  private void showSettingsPopup() {
    ConfigPopup.show(config.compact ? settingsQuad : settingsHalf, 0, -calcMenuHeight(ConfigPopup));
  }

  private void getWAPList() {
    jbusClient.call("org.jflinux.jnetworkmgr", "getWAPList", JBusClient.quote(jbusClient.pack));
  }

  private void getVPNList() {
    jbusClient.call("org.jflinux.jnetworkmgr", "getVPNList", JBusClient.quote(jbusClient.pack));
  }

  private static class WAP {
    public String dev, ssid, encType;
  }
  ArrayList<WAP> wapItems = new ArrayList<WAP>();

  private void showNetworkPopup() {
    NetworkPopup.removeAll();
    if (checkWireless()) {
      JMenu subWireless = new JMenu("Wireless");
      NetworkPopup.add(subWireless);
      //list wireless access points
      String lns[] = wapList.split("[|]");
      boolean wapActive = false;
      int idx = 0;
      String dev = null;
      int cnt = 0;
      int w = 0;
      wapItems.clear();
      while (idx < lns.length) {
        if (cnt == 0) {
          dev = lns[idx++];
          cnt = JF.atoi(lns[idx++]);
        }
        if (cnt == 0) continue;
        String ssid = lns[idx++];
        String encType = lns[idx++];
        WAP wap = new WAP();
        if (ssid.endsWith(" *")) {
          wapActive = true;
        }
        wap.dev = dev;
        wap.ssid = ssid;
        wap.encType = encType;
        wapItems.add(wap);
        JMenuItem w1 = new JMenuItem(ssid);
        w1.setActionCommand("#wap#" + (w++));
        w1.addActionListener(this);
        addMouseListener(w1);
        subWireless.add(w1);
        cnt--;
      }
      if (wapActive) {
        JMenuItem w1 = new JMenuItem("Disconnect...");
        w1.setActionCommand("#wap-disconnect");
        w1.addActionListener(this);
        addMouseListener(w1);
        subWireless.add(w1);
      }
      addMouseListener(subWireless);
    }

    if (checkBluetooth()) {
      JMenu subBluetooth = new JMenu("Bluetooth");
      NetworkPopup.add(subBluetooth);
      for(int a=0;a<btDevices.size();a++) {
        String desc = btDevices.get(a).desc;
        JMenuItem b1 = new JMenuItem(desc);
        b1.setActionCommand("#bt#" + desc);
        b1.addActionListener(this);
        addMouseListener(b1);
        subBluetooth.add(b1);
      }
      JMenuItem b1 = new JMenuItem("Connect...");
      b1.setActionCommand("#bt-connect");
      b1.addActionListener(this);
      addMouseListener(b1);
      subBluetooth.add(b1);
      addMouseListener(subBluetooth);
    }

    //list vpn
    JMenu subVPN = new JMenu("VPN");
    NetworkPopup.add(subVPN);
    String vpns[] = vpnList.split("[|]");
    for(int a=0;a<vpns.length;a++) {
      if (vpns[a].length() == 0) continue;
      String name = vpns[a];
      JMenuItem vpn1 = new JMenuItem(name);
      vpn1.setActionCommand("#vpn#" + vpns[a]);
      vpn1.addActionListener(this);
      addMouseListener(vpn1);
      subVPN.add(vpn1);
    }
    JMenuItem vpn1 = new JMenuItem("Configure...");
    vpn1.setActionCommand("#vpn-configure");
    vpn1.addActionListener(this);
    addMouseListener(vpn1);
    subVPN.add(vpn1);

    addMouseListener(NetworkPopup);
    addMouseListener(subVPN);
    NetworkPopup.show(config.compact ? networkQuad : networkHalf, 0, -calcMenuHeight(NetworkPopup));
  }

  private void emptyTrash() {
    JFLog.log("empty trash");
    File file = new File(System.getenv("HOME") + "/.local/share/Trash");
    //glob files
    ArrayList<String> cmd = new ArrayList<String>();
    cmd.add("jrm");
    File files[] = file.listFiles();
    if (files == null) return;
    for(int a=0;a<files.length;a++) {
      cmd.add(files[a].getAbsolutePath());
    }
    try {
      Runtime.getRuntime().exec(cmd.toArray(new String[0]));
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private static String months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"};

  private void setClock() {
    Calendar now = Calendar.getInstance();
    clockLine1 = String.format("%s %s", months[now.get(Calendar.MONTH)], now.get(Calendar.DAY_OF_MONTH));
    int hour = now.get(Calendar.HOUR);
    if (hour == 0) hour = 12;
    clockLine2 = String.format("%d:%02d", hour, now.get(Calendar.MINUTE));
    clockImage.repaint();
  }

  public void updateClock() {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        setClock();
      }
    });
  }

  public void setupClockTimer() {
    if (clockTimer != null) clockTimer.cancel();
    Calendar now = Calendar.getInstance();
    clockTimer = new java.util.Timer();
    clockTimer.scheduleAtFixedRate(new TimerTask() {public void run() {updateClock();}}
      ,60000 - now.get(Calendar.SECOND) * 1000, 60000);
  }

  private void calcDockHeight() {
    int pos = dockSliderPos;
    dockHeight = (panelHeight-borderSize) * pos / 100;
    if (dockHeight == 0) dockHeight = 1;
  }

  private class DockSliderTask extends TimerTask {
    public void run() {
      try {
        synchronized(sliderLock) {
          if (dockSliderShow) {
            if (dockSliderPos == 100) {
              dockSliderTimer.cancel();
              dockSliderTimer = null;
            } else {
              dockSliderPos += 25;
              calcDockHeight();
              setPosition();
            }
          } else {
            if (dockSliderPos == 0) {
              dockSliderTimer.cancel();
              dockSliderTimer = null;
            } else {
              dockSliderPos -= 25;
              calcDockHeight();
              setPosition();
            }
          }
          if (keyboardShown) {
            keyboardWindow.setPosition(keyboardHeight, dockHeight);
          }
        }
      } catch (Exception e) {
        JFLog.log(e);
      }
    }
  }

  private void removeNewButton() {
    buttons.remove(newButton);
    newButton = null;
    appIdx--;
    buttonsCount--;
  }

  private java.util.Timer dockSliderTimer = null;
  private final Object sliderLock = new Object();
  private int dockSliderPos = 0;  //percentage of dock visible
  private int dockHeight = 0;  //actual size
  private boolean dockSliderShow = true;

  private void hideDock() {
    if (newButton != null) {
      removeNewButton();
    }
    if (!config.autoHide) return;
    if (isDockHidden) return;
    isDockHidden = true;
    if (newButton != null) {
      removeNewButton();
    }
    if (soundWindow != null) {
      hideSound();
    }
    if (calWindow != null) {
      hideCalendar();
    }
    if (PowerPopup.isVisible()) PowerPopup.setVisible(false);
    if (ConfigPopup.isVisible()) ConfigPopup.setVisible(false);
    if (NetworkPopup.isVisible()) NetworkPopup.setVisible(false);
    synchronized(sliderLock) {
      getScreenSize();
      dockSliderShow = false;
      if (dockSliderTimer == null) {
        dockSliderTimer = new java.util.Timer();
        dockSliderTimer.schedule(new DockSliderTask(), 50, 50);
      }
    }
  }

  private void showDock() {
    if (!isDockHidden) return;
    isDockHidden = false;
    synchronized(sliderLock) {
      dockSliderShow = true;
      if (dockSliderTimer == null) {
        dockSliderTimer = new java.util.Timer();
        dockSliderTimer.schedule(new DockSliderTask(), 50, 50);
      }
    }
    if (!haveMouse) {
      autoHideTimer = new java.util.Timer();
      autoHideTimer.schedule(new TimerTask() {
        public void run() {
          if (!haveMouse && !isDockHidden && drag == null) hideDock();
        }
      }, 5*1000);
    }
  }

  private class KeyboardSliderTask extends TimerTask {
    public void run() {
      synchronized(sliderLock) {
        if (keyboardSliderShow) {
          if (keyboardHeight == 200) {
            keyboardSliderTimer.cancel();
            keyboardSliderTimer = null;
          } else {
            keyboardHeight += 25;
            keyboardWindow.setPosition(keyboardHeight, dockHeight);
          }
        } else {
          if (keyboardHeight == 0) {
            keyboardSliderTimer.cancel();
            keyboardSliderTimer = null;
            keyboardWindow.setVisible(false);
            keyboardShown = false;
          } else {
            keyboardHeight -= 25;
            keyboardWindow.setPosition(keyboardHeight, dockHeight);
          }
        }
      }
    }
  }

  private java.util.Timer keyboardSliderTimer = null;
  private int keyboardHeight = 0;  //actual size of keyboard height (1-200)
  private boolean keyboardSliderShow = true;

  private void showKeyboard() {
    if (keyboardShown) return;
    synchronized(sliderLock) {
      keyboardWindow.setPosition(1, dockHeight);
      keyboardWindow.setVisible(true);
      keyboardShown = true;
      keyboardSliderShow = true;
      if (keyboardSliderTimer == null) {
        keyboardSliderTimer = new java.util.Timer();
        keyboardSliderTimer.schedule(new KeyboardSliderTask(), 50, 50);
      }
    }
  }

  public void hideKeyboard() {
    if (!keyboardShown) return;
    synchronized(sliderLock) {
      keyboardSliderShow = false;
      if (keyboardSliderTimer == null) {
        keyboardSliderTimer = new java.util.Timer();
        keyboardSliderTimer.schedule(new KeyboardSliderTask(), 50, 50);
      }
    }
  }

  private void showCalendar() {
    if (soundWindow != null) hideSound();
    calWindow = new CalendarWindow(clockImage.getLocationOnScreen(), clockImage.getWidth());
    calWindow.setVisible(true);
    calWindow.addMouseListener(this);
  }

  private void hideCalendar() {
    calWindow.setVisible(false);
    calWindow.dispose();
    calWindow = null;
  }

  private void showSound() {
    if (calWindow != null) hideCalendar();
    Point pos;
    int width;
    if (config.compact) {
      pos = soundQuad.getLocationOnScreen();
      width = soundQuad.getWidth();
    } else {
      pos = soundHalf.getLocationOnScreen();
      width = soundHalf.getWidth();
    }
    //sometimes pos.y is wrong so calc it instead
    pos.y = sy - buttonHeight + 1;
    soundWindow = new SoundWindow(pos, width);
    soundWindow.setVisible(true);
    soundWindow.addMouseListener(this);
    soundWindow.reload(config);
  }

  private void hideSound() {
    soundWindow.setVisible(false);
    soundWindow.dispose();
    soundWindow = null;
  }

  public void mouseClicked(MouseEvent me) {
    if (me.getSource() == buttons) {
      if (x11id == 0) return;
      try {
        x11_set_dock();
      } catch (Throwable t) {
        JFLog.log(t);
      }
    }
  }

  public void mousePressed(MouseEvent me) {
//    JFLog.log("press:" + me);
    draggable = me.getButton() == MouseEvent.BUTTON1;
    if (me.getSource() == this && me.getButton() == MouseEvent.BUTTON3) {
      TaskbarPopup.show(this, me.getX(), me.getY());
    }
  }

  private volatile boolean showDockTimerActive = false;

  public void mouseEntered(MouseEvent me) {
    haveMouse = true;
    if (autoHideTimer != null) {
      autoHideTimer.cancel();
      autoHideTimer = null;
    }
    if ((isDockHidden) && (!showDockTimerActive)) {
      showDockTimerActive = true;
      new java.util.Timer().schedule(new TimerTask() {
        public void run() {
          showDockTimerActive = false;
          if (haveMouse && isDockHidden) showDock();
        }
      }, 250);
    }
  }

  private volatile boolean hideDockTimerActive = false;

  public void mouseExited(MouseEvent me) {
    haveMouse = false;
    if ((!isDockHidden) && (!hideDockTimerActive)) {
      hideDockTimerActive = true;
      new java.util.Timer().schedule(new TimerTask() {
        public void run() {
          hideDockTimerActive = false;
          if (!haveMouse && !isDockHidden && drag == null) hideDock();
        }
      }, 250);
    }
  }

  public void mouseReleased(MouseEvent me) {
//    JFLog.log("release:" + me);
    try {
      draggable = false;
      if (drag == null) return;
      drag.setBackground(drag_org_color);
      drag = null;
      getContentPane().setCursor(Cursor.getDefaultCursor());
      updateAppList();
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private JButton drag;
  private int dragx, dragy;
  private int drag_width;
  private Color drag_org_color;
  private int drag_component_idx;
  public boolean dragged, draggable;

  public void mouseDragged(MouseEvent me) {
    if (!draggable) return;
    dragged = true;  //avoid click after dragging (not working)
    try {
      //move Button
      if (drag == null) {
        //start drag
        JButton button = (JButton)me.getSource();
        String file = (String)button.getClientProperty("file");
        if ((file == null) || (file.startsWith("#"))) return;
        drag = button;
        dragx = me.getXOnScreen();
        dragy = me.getYOnScreen();
        drag_width = button.getWidth();
        getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        drag_org_color = button.getBackground();
        button.setBackground(Color.green);
        drag_component_idx = -1;
        int cnt = buttons.getComponentCount();
        for(int a=0;a<cnt;a++) {
          if (buttons.getComponent(a) == drag) {
            drag_component_idx = a;
            break;
          }
        }
      } else {
        //move drag
        if (drag_component_idx == -1) return;  //error
        int newX = me.getXOnScreen();
        if (needArrows) {
          newX += halfWidth;
        }
        int newIdx = newX / buttonWidth;
        if (newIdx < 1) newIdx = 1;
        if (newIdx >= appIdx) newIdx = appIdx-1;
        if (newIdx == drag_component_idx) return;
        drag_component_idx = newIdx;
        buttons.setComponentZOrder(drag, newIdx);
        updateButtons();
      }
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  public void mouseMoved(MouseEvent me) {
  }

  private static void doRun() {
    try {
      RunDialog runDialog = new RunDialog(null, true);
      runDialog.setVisible(true);
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private void addMouseListener(JComponent jc) {
    jc.addMouseListener(this);
    int cnt = jc.getComponentCount();
    for(int a=0;a<cnt;a++) {
      jc.getComponent(a).addMouseListener(this);
    }
  }

  private void closeAllApps() {
    saveConfig();
    //close open VPN connections
    jbusClient.call("org.jflinux", "closeAllVPN", "");
    //close all apps belonging to this user
    //BUG : killall may kill other sessions of same user???
    try {
      Runtime.getRuntime().exec(new String[] {"killall", "-u", System.getenv("USER")});
    } catch (Exception e) {JFLog.log(e);}
  }

  public void showConfig() {
    ConfigDialog dialog = new ConfigDialog(null, true, config);
    dialog.setVisible(true);
    if (dialog.accepted) {
      dialog.updateConfig(config);
      updateConfig();
      saveConfig();
    }
  }

  private void checkBattery() {
    ShellProcess sp = new ShellProcess();
    String output = sp.run(new String[] {"acpi"}, false);
    if (output.indexOf("Battery") == -1) return;
    batteryImage = IconCache.loadIcon("jfdesktop-battery");
    try {
      tray = SystemTray.getSystemTray();
      trayIcon = new TrayIcon(batteryImage.getImage(), "Battery", BatteryPopup);
      tray.add(trayIcon);
    } catch (Exception e) {
      JFLog.log(e);
      return;
    }
    new java.util.Timer().schedule(new TimerTask() {public void run() {updateBattery();}}, 0, 60000);
  }

  private boolean checkBluetooth() {
    ShellProcess sp = new ShellProcess();
    String output = sp.run(new String[] {"hciconfig"}, false);
    if (output.indexOf("hci") == -1) return false;
    return true;
  }

  private boolean checkWireless() {
    ShellProcess sp = new ShellProcess();
    String output = sp.run(new String[] {"iwconfig"}, false);
    if (output.indexOf("ESSID") == -1) return false;
    return true;
  }

  private JFImage plugImage;

  private void updateBattery() {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        ShellProcess sp = new ShellProcess();
        String output = sp.run(new String[] {"acpi"}, false);
        String lns[] = output.split("\n");
        String f[] = lns[0].split(",");  //only show battery #1
        //Battery 1: discharging, 44%, 00:18:48 remaining
        int percent = JF.atoi(f[1].trim().replaceAll("%", ""));
        Graphics g = batteryImage.getGraphics();
        g.setColor(Color.white);
        g.fillRect(4,2,15,20);
        int h = percent * 20 / 100;
        int y = 2 + (20-h);
        if (percent < 15) {
          g.setColor(Color.yellow);
        } else if (percent < 5) {
          g.setColor(Color.red);
        } else {
          g.setColor(Color.green);
        }
        g.fillRect(4,y,15,h);
        if (lns[0].indexOf("Discharging") == -1) {
          if (plugImage == null) {
            plugImage = IconCache.loadIcon("jfdesktop-plug");
          }
          g.drawImage(plugImage.getImage(), 0, 0, null);
        }
        trayIcon.setImage(batteryImage.getImage());
      }
    });
  }

  //LayoutManager

  public void addLayoutComponent(String string, Component cmpnt) {
  }

  public void removeLayoutComponent(Component cmpnt) {
  }

  public Dimension preferredLayoutSize(Container p) {
    if (isDockHidden) {
      return new Dimension(sx, panelHiddenHeight);
    }
    return new Dimension(sx, panelHeight + borderSize*2);
  }

  public Dimension minimumLayoutSize(Container p) {
    if (isDockHidden) {
      return new Dimension(sx, panelHiddenHeight);
    }
    return new Dimension(sx, panelHeight + borderSize*2);
  }

  public void layoutContainer(Container p) {
    try {
      if (p == getContentPane()) {
        synchronized(p.getParent().getTreeLock()) {
          buttons.setBounds(0,0,sx,panelHeight);
        }
        return;
      }
      synchronized(p.getParent().getTreeLock()) {
        Dimension d;
        //do we need arrows?
        int tWidth = 0, bWidth = 0;  //tools width, buttons width
        bWidth += appIdx * buttonWidth;
        if (config.showClock) {
          d = clockImage.getPreferredSize();
          tWidth += d.width;
        }
        if (config.showKeyboard) {
          d = keyboard.getPreferredSize();
          tWidth += d.width;
        }
        if (config.compact) {
          //quad controls
          tWidth += buttonWidth;
        } else {
          //half controls
          tWidth += halfWidth * 4;
        }
        tWidth += buttonWidth;  //trash
        int trayWidth = Linux.x11_tray_width();
  //      JFLog.log("dock widths:" + bWidth + "," + trayWidth + "," + tWidth);
        int cnt = 0;
        if (bWidth + trayWidth + tWidth > sx) {
          //need arrow buttons
          if (!needArrows) {
            needArrows = true;
            buttons.add(left,getLastIdx());
            buttons.add(right,getLastIdx());
          }
          int arrowsWidth = halfWidth * 2;  //left + right arrows
          int appsWidth = buttonWidth;
          cnt = (sx - appsWidth - tWidth - arrowsWidth - trayWidth) / buttonWidth;
          bWidth = cnt * buttonWidth;
          overflowCnt = buttonsCount - cnt - viewIdx;
        } else {
          //no arrows - show all buttons
          if (needArrows) {
            needArrows = false;
            buttons.remove(left);
            buttons.remove(right);
          }
          cnt = buttonsCount;
          viewIdx = 0;
          overflowCnt = 0;
        }
        int cx = 0;
        apps.setBounds(cx, borderSize, buttonWidth, buttonHeight);
        cx += buttonWidth;
        if (needArrows) {
          left.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
          for(int a=1;a<=viewIdx;a++) {
            p.getComponent(a).setVisible(false);
          }
        }
        int idx = 1 + viewIdx;
        while (cnt > 0) {
          Component c = p.getComponent(idx++);
          c.setVisible(true);
          c.setBounds(cx, borderSize, buttonWidth, buttonHeight);
          cx += buttonWidth;
          cnt--;
        }
        while (idx < appIdx) {
          p.getComponent(idx++).setVisible(false);
        }
        if (needArrows) {
          right.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
        }
        cx = sx - tWidth;
        trayPos = cx;
        Linux.x11_tray_reposition(sx, trayPos, buttonHeight + trayPad);
        if (config.compact) {
          quad.setBounds(cx, borderSize, buttonWidth, buttonHeight);
          cx += buttonWidth;
        } else {
          powerHalf.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
          settingsHalf.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
          networkHalf.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
          soundHalf.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
        }
        if (config.showKeyboard) {
          keyboard.setBounds(cx, borderSize, halfWidth, buttonHeight);
          cx += halfWidth;
        }
        trash.setBounds(cx, borderSize, buttonWidth, buttonHeight);
        cx += buttonWidth;
        if (config.showClock) {
          d = clockImage.getPreferredSize();
          clockImage.setBounds(cx, borderSize, d.width, buttonHeight);
        }
      }
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private void disconnectVPN(String name) {
    jbusClient.call("org.jflinux.jnetworkmgr", "disconnectVPN", JBusClient.quote(name));
  }

  private void connectVPN(String name) {
    if (name.endsWith(" *")) {
      disconnectVPN(name.substring(0, name.length() - 2));
    } else {
      startNetworkTimer("cancelVPN");
      jbusClient.call("org.jflinux.jnetworkmgr", "connectVPN", JBusClient.quote(jbusClient.pack) + "," + JBusClient.quote(name));
    }
  }

  private void disconnectWAP(String ssid) {
    jbusClient.call("org.jflinux.jnetworkmgr", "disconnectWAP" , JBusClient.quote(ssid));
  }

  private void connectWAP(String dev, String ssid, String encType) {
    if (ssid.endsWith(" *")) {
      disconnectWAP(ssid.substring(0, ssid.length() - 2));
    } else {
      String key = "";
      if (encType.equals("WEP")) {
        key = JFAWT.getString("Enter WEP encryption key (26 or 10 hex digits)", "");
      } else if (encType.equals("WPA")) {
        key = JFAWT.getString("Enter WPA pass phrase", "");
      }
      startNetworkTimer("cancelWAP");
      jbusClient.call("org.jflinux.jnetworkmgr", "connectWAP",
        JBusClient.quote(jbusClient.pack) + "," + JBusClient.quote(dev) + "," + JBusClient.quote(ssid) + "," + JBusClient.quote(encType) + "," + JBusClient.quote(key));
    }
  }

  private void blueToothDisconnect(String dev) {
    for(int a=0;a<btDevices.size();a++) {
      if (btDevices.get(a).desc.equals(dev)) {
        ShellProcess sp = new ShellProcess();
        String output = sp.run(new String[] {"hcitool", "dc", btDevices.get(a).mac}, false);
//        if (output.startsWith("Not connected")) {}
        btDevices.remove(a);
      }
    }
  }

  private java.util.Timer networkTimer;
  private int networkIconIdx;
  private JFImage networkIconsQuad[];
  private JFImage networkIconsHalf[];
  private Icon networkOrgIcon;
  private final Object networkLock = new Object();
  private int networkTimerCount;
  private String cancelNetworkMethod;

  private void loadNetworkIcons() {
    halfSized = true;
    networkIconsQuad = new JFImage[3];
    networkIconsHalf = new JFImage[3];
    for(int a=0;a<3;a++) {
      JFImage img = IconCache.loadIcon("jfdesktop-network-" + (a+1));
      networkIconsQuad[a] = IconCache.scaleIcon(img, 21, 21);
      JFImage tmpImage = new JFImage(bx, by);
      tmpImage.fill(0, 0, bx, by, 0, true);
      tmpImage.getGraphics().drawImage(IconCache.scaleIcon(img, 24, 24).getImage(), 0,(by-bx)/2 + 4, null);
      tmpImage.getGraphics().drawImage(arrowImage.getImage(), 0,0, null);
      networkIconsHalf[a] = tmpImage;
    }
    halfSized = false;
  }

  private void startNetworkTimer(String cancelMethod) {
    if (networkTimer != null) return;
    cancelNetworkMethod = cancelMethod;
    networkIconIdx = 0;
    networkTimerCount = 0;
    networkTimer = new java.util.Timer();
    networkTimer.schedule(new TimerTask() {
      public void run() {
        synchronized(networkLock) {
          if (networkTimer == null) return;
          networkTimerCount++;
          if (networkTimerCount == 60) {
            //after 30 seconds
            JFLog.log("30 seconds timeout network connection");
            networkTimer.cancel();
            networkTimer = null;
            resetNetworkIcon();
            cancelNetworkConnection();
            return;
          }
          try {
            java.awt.EventQueue.invokeLater(new Runnable() {
              public void run() {
                if (config.compact) {
                  if (networkOrgIcon == null) networkOrgIcon = networkQuad.getIcon();
                  networkIconIdx++;
                  if (networkIconIdx == networkIconsQuad.length) networkIconIdx = 0;
                  networkQuad.setIcon(networkIconsQuad[networkIconIdx]);
                } else {
                  if (networkOrgIcon == null) networkOrgIcon = networkHalf.getIcon();
                  networkIconIdx++;
                  if (networkIconIdx == networkIconsHalf.length) networkIconIdx = 0;
                  networkHalf.setIcon(networkIconsHalf[networkIconIdx]);
                }
              }
            });
          } catch (Exception e) {}
        }
      }
    }, 0, 500);
  }

  private void stopNetworkTimer() {
    synchronized(networkLock) {
      cancelNetworkMethod = null;
      networkTimer.cancel();
      networkTimer = null;
    }
    resetNetworkIcon();
  }

  private void resetNetworkIcon() {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        if (config.compact) {
          networkQuad.setIcon(networkOrgIcon);
        } else {
          networkHalf.setIcon(networkOrgIcon);
        }
        networkOrgIcon = null;
      }
    });
  }

  private void cancelNetworkConnection() {
    if (cancelNetworkMethod == null) return;
    stopNetworkTimer();
    jbusClient.call("org.jflinux.jnetworkmgr", cancelNetworkMethod, "");
    cancelNetworkMethod = null;
    showNetworkFailed();
  }

  private void showNetworkFailed() {
    JFAWT.showError("Error", "Connection failed!");
  }

  private void showWelcome() {
    try {
      Runtime.getRuntime().exec("jwelcome");
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private void mkdirs() {
    new File(JF.getUserPath() + "/Desktop").mkdir();
    new File(JF.getUserPath() + "/Documents").mkdir();
    new File(JF.getUserPath() + "/Music").mkdir();
    new File(JF.getUserPath() + "/Pictures").mkdir();
    new File(JF.getUserPath() + "/Videos").mkdir();
    new File(JF.getUserPath() + "/Downloads").mkdir();
  }

  public void soundinit() {
    new Thread() {
      public void run() {
        SoundWindow.init(config);
        saveConfig();
      }
    }.start();
  }

  public void initUpgrades() {
    try {
      File file = new File("/etc/upgrades.cnts");
      if (!file.exists()) return;
      FileInputStream fis = new FileInputStream(file);
      String str = new String(JF.readAll(fis));
      fis.close();
      if (str.length() == 0) return;
      int idx = str.indexOf(" ");
      if (idx == -1) return;
      int cnts = JF.atoi(str.substring(0, idx));
      setUpgradesAvailable(cnts);
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  public void setUpgradesAvailable(int cnt) {
    for(int a=0;a<ConfigPopup.getComponentCount();a++) {
      JMenuItem mi = (JMenuItem)ConfigPopup.getComponent(a);
      if (mi.getText().startsWith("Upgrades")) {
        mi.setText("Upgrades (" + cnt + ")");
        break;
      }
    }
  }

  public void x11_set_dock() {
    try {
      Linux.x11_set_dock(x11id);
    } catch (Throwable t) {
      JFLog.log(t);
    }
  }

  public int getPanelHeight() {
    return panelHeight;
  }

  private void initTrashDND() {
    trash.setTransferHandler(new TransferHandler() {
      public boolean canImport(TransferHandler.TransferSupport info) {
        // we only import Files
        if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
          return false;
        }

//        TransferHandler.DropLocation dl = (TransferHandler.DropLocation) info.getDropLocation();
//        Point pt = dl.getDropPoint();
        return true;
      }

      public boolean importData(TransferHandler.TransferSupport info) {
        if (!info.isDrop()) {
          return false;
        }

        // Check for file flavor
        if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
          return false;
        }

//        TransferHandler.DropLocation dl = info.getDropLocation();
//        Point pt = dl.getDropPoint();
        String folder = JF.getUserPath() + "/.local/share/Trash";

        // Get the file(s) that are being dropped.
        Transferable t = info.getTransferable();
        java.util.List<File> data;
        try {
          data = (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
        } catch (Exception e) {
          return false;
        }

        // Perform the actual import.
        ArrayList<String> cmd = new ArrayList<String>();
        boolean move = false;
        boolean copy = false;
        for(int a=0;a<data.size();a++) {
          switch (info.getDropAction()) {
            case COPY:
            case MOVE:
              if (copy) return false;  //Can that happen?
              move = true;
              cmd.add(((File)data.get(a)).getAbsolutePath());
              break;
            case LINK:
              return false;  //BUG : not supported : ???
          }
        }
        if (cmd.isEmpty()) return false;
        cmd.add(0, "-v");
        cmd.add(0, "-n");
        if (copy) {
          cmd.add(0, "cp");
        } else if (move) {
          cmd.add(0, "mv");
        } else {
          return false;
        }
        cmd.add(folder);
        JFileBrowser.runCmd(null, cmd.toArray(new String[0]));
        return true;
      }

      public int getSourceActions(JComponent c) {
        return COPY_OR_MOVE;
      }

      protected Transferable createTransferable(JComponent c) {
        //can not transfer out of trash from this icon
        return null;
      }

      protected void exportDone(JComponent source, Transferable data, int action) {
      }
    });
  }

  private int getDragIdx(Point pt) {
    int x = pt.x;
    if (needArrows) {
      x += halfWidth;
    }
    x /= buttonWidth;
    if (x < 1) return 1;
    if (x >= appIdx) return appIdx-1;
    return x;
  }

  private void initDockDND() {
    this.setTransferHandler(new TransferHandler() {
      public boolean canImport(TransferHandler.TransferSupport info) {
        // we only import Files
        if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
          return false;
        }

        TransferHandler.DropLocation dl = (TransferHandler.DropLocation) info.getDropLocation();
        Point pt = dl.getDropPoint();

        if (newButton == null) {
          newButtonIdx = getDragIdx(pt);
          newButton = addButton("jfdesktop-new", "New", "#new", newButtonIdx, false);
          appIdx++;
          buttonsCount++;
        } else {
          int newIdx = getDragIdx(pt);
          if (newIdx == newButtonIdx) return true;  //no change
          newButtonIdx = newIdx;
          buttons.setComponentZOrder(newButton, newButtonIdx);
        }
        mouseEntered(null);  //prevent dock for hiding

        updateButtons();
        return true;
      }

      public boolean importData(TransferHandler.TransferSupport info) {
        if (!info.isDrop()) {
          return false;
        }

        // Check for file flavor
        if (!info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
          return false;
        }

        TransferHandler.DropLocation dl = info.getDropLocation();
        Point pt = dl.getDropPoint();
        int idx = getDragIdx(pt);

        // Get the file(s) that are being dropped.
        Transferable t = info.getTransferable();
        java.util.List<File> data;
        try {
          data = (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
        } catch (Exception e) {
          return false;
        }

        if (newButton != null) {
          removeNewButton();
        }

        // Perform the actual import.
        String file;
        for(int a=0;a<data.size();a++) {
          switch (info.getDropAction()) {
            case COPY:
            case MOVE:
              file = ((File)data.get(a)).getAbsolutePath();
              if (!file.endsWith(".desktop")) continue;
              if (!haveApp(file)) {
                addButton(file, true, idx);
                addApp(file);
              }
              break;
            case LINK:
              //BUG : not supported : ???
              continue;
          }
        }
        saveConfig();
        updateButtons();
        return true;
      }

      public int getSourceActions(JComponent c) {
        return COPY_OR_MOVE;
      }

      protected Transferable createTransferable(JComponent c) {
        //can not transfer out of trash from this icon
        return null;
      }

      protected void exportDone(JComponent source, Transferable data, int action) {
      }
    });
  }

  private void loadMappings() {
    Mappings.loadMaps();
    Mappings.Maps maps = Mappings.getMaps();
    if (maps == null || maps.map == null) return;
    JBusMethods methods = new JBusMethods();
    for(int a=0;a<maps.map.length;a++) {
      methods.mount(null, maps.map[a].uri, maps.map[a].mount, maps.map[a].passwd, maps.map[a].wineDrive);
    }
  }

  private void openButton(JButton button) {
    button.putClientProperty("open", "true");  //ensure a NEW window is opened (not just select already active window)
    button.doClick();
    button.putClientProperty("open", null);
  }

  private void createAppPopupMenu(JButton button) {
    JPopupMenu popup = new JPopupMenu();
    JMenuItem item;
    Boolean pinned = (Boolean)button.getClientProperty("pinned");
    Group group = (Group)button.getClientProperty("group");

    item = new JMenuItem(group.isEmpty() ? "Open" : "Open new window");
    item.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JMenuItem mi = (JMenuItem)e.getSource();
        JPopupMenu pm = (JPopupMenu)mi.getParent();
        openButton((JButton)pm.getInvoker());
      }
    });
    popup.add(item);

    for(int a=0;a<group.size();a++) {
      final Window window = group.getWindow(a);
      item = new JMenuItem(window.title);
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          window.show();
        }
      });
      popup.add(item);
    }

    item = new JMenuItem("Copy to Desktop");
    item.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JMenuItem mi = (JMenuItem)e.getSource();
        JPopupMenu pm = (JPopupMenu)mi.getParent();
        copyButtonToDesktop((JButton)pm.getInvoker());
      }
    });
    popup.add(item);

    item = new JMenuItem(pinned ? "Unpin" : "Pin");
    item.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JMenuItem mi = (JMenuItem)e.getSource();
        JPopupMenu pm = (JPopupMenu)mi.getParent();
        togglePinned((JButton)pm.getInvoker());
      }
    });
    popup.add(item);

    addMouseListener(popup);

    button.setComponentPopupMenu(popup);
  }

  private void updateAppList() {
    ArrayList<App> list = new ArrayList<App>();
    Component buts[] = buttons.getComponents();
    for(int a=0;a<buts.length;a++) {
      if (!(buts[a] instanceof JButton)) continue;
      JButton button = (JButton)buts[a];
      String file = (String)button.getClientProperty("file");
      if (file.startsWith("#")) continue;
      Boolean pinned = (Boolean)button.getClientProperty("pinned");
      if (!pinned) continue;
      App app = new App();
      app.file = (String)button.getClientProperty("file");
      list.add(app);
    }
    config.dock = list.toArray(new App[0]);
    saveConfig();
  }
  private void updateButtons() {
    buttons.revalidate();
    buttons.repaint();
  }

  public class JBusMethods {
    public void run() {
      doRun();
    }
    public void show() {
      showDock();
    }
    public void getWelcome(String pack) {
      jbusClient.call(pack, "setWelcome", JBusClient.quote("" + config.welcome));
    }
    public void setWelcome(String state) {
      config.welcome = state.equals("true");
      saveConfig();
    }
    public void upgradesAvailable(int upgrades) {
      setUpgradesAvailable(upgrades);
    }
    public void volume(String lvl) {
      if ((lvl == null) || (lvl.length() == 0)) return;
      if (isDockHidden) showDock();
      if (soundWindow == null) showSound();
      if (lvl.equals("mute")) {
        soundWindow.mute();
      } else if (lvl.charAt(0) == '+') {
        soundWindow.adjustVolume(JF.atoi(lvl.substring(1)));
      } else if (lvl.charAt(0) == '-') {
        soundWindow.adjustVolume(-JF.atoi(lvl.substring(1)));
      }
    }
    public void vpnFailed(String id) {
      stopNetworkTimer();
      showNetworkFailed();
    }
    public void vpnSuccess(String id) {
      stopNetworkTimer();
    }
    public void wapFailed() {
      stopNetworkTimer();
      showNetworkFailed();
    }
    public void wapSuccess() {
      stopNetworkTimer();
    }
    public void btFailed() {
      if (btDialog == null) return;
      btDialog.failed();
    }
    public void btSuccess() {
      if (btDialog == null) return;
      btDialog.success();
    }
    public void setBTdevices(String list) {
      if (btDialog == null) return;
      btDialog.devices(list);
    }
    public void soundSinkVolumeChanged(int idx, int volume) {
      if (idx == PulseAudio.sinks.get(0).idx) SoundWindow.setVolume(volume);
      for(int a=0;a<config.sink.length;a++) {
        if (config.sink[a].idx == idx) {
          config.sink[a].volume = volume;
          saveConfig();
          return;
        }
      }
    }
    public void soundSourceVolumeChanged(int idx, int volume) {
      for(int a=0;a<config.source.length;a++) {
        if (config.source[a].idx == idx) {
          config.source[a].volume = volume;
          saveConfig();
          return;
        }
      }
    }
    public void soundSinkPortChanged(int idx, String port) {
      for(int a=0;a<config.sink.length;a++) {
        if (config.sink[a].idx == idx) {
          config.sink[a].activePort = port;
          saveConfig();
          return;
        }
      }
    }
    public void soundSourcePortChanged(int idx, String port) {
      for(int a=0;a<config.source.length;a++) {
        if (config.source[a].idx == idx) {
          config.source[a].activePort = port;
          saveConfig();
          return;
        }
      }
    }
    public void soundProfileChanged(int idx, String profile) {
      for(int a=0;a<config.card.length;a++) {
        if (config.card[a].idx == idx) {
          config.card[a].activeProfile = profile;
          saveConfig();
          return;
        }
      }
    }
    public void timeAdjusted() {
      setupClockTimer();
    }
    public void videoChanged(String reason) {
      //TODO : popup window asking to load jconfig:display
      if (reason.equals("jconfig")) {
        updateConfig();
      } else {
        //else udev event : need to call x11_rr_auto first
        java.awt.EventQueue.invokeLater(new Runnable() {
          public void run() {
            Linux.x11_rr_auto();
            updateConfig();
          }
        });
      }
    }
    public void powerChanged() {
      updateBattery();
    }
    public void mount(String callback, String uri, String mount, String pass, String link) {
      JFLog.log("mount:" + uri + "," + mount);
      final String _callback = callback;
      final String _uri = uri;
      new Thread() {
        public void run() {
          try {
            Process p = Runtime.getRuntime().exec(new String[] {"gnome-disk-image-mounter", _uri});
            p.waitFor();
            int result = p.exitValue();
            if (_callback != null) jbusClient.call(_callback, result == 0 ? "mountSuccess" : "mountFail", JBusClient.quote(_uri));
          } catch (Exception e) {
            JFLog.log(e);
          }
        }
      }.start();
    }
    public void setWAPList(String list) {
      wapList = list;
    }
    public void setVPNList(String list) {
      vpnList = list;
      showNetworkPopup();
    }
    //jfile : file ops (should use clipboard)
    public void setFileSelection(String selection) {
      JFLog.log("setSelection:" + selection);
      fileSelection = selection;
    }
    public void getFileSelection(String pack) {
      if (fileSelection == null) return;
      JFLog.log("getSelection:" + pack);
      jbusClient.call(pack, "getFileSelection", JBusClient.quote(fileSelection));
    }
    public void clearFileSelection() {
      JFLog.log("clearSelection");
      fileSelection = null;
    }
  }

  public void get() {
    if (fileSelection == null) return;
    Desktop.desktop.browser.paste(JBusClient.decodeString(fileSelection));
  }
  public void set(String fileset) {
    fileSelection = fileset;
  }
  public void clear() {
    fileSelection = "";
  }
}
